package com.dylan.magic.redis.config.redis;

import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;

import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author : ankang
 * @date : 2019/12/14 18:00
 */
public class MagicValueOperations implements ValueOperations<String, String> {

    private ValueOperations<String, String> valueOperations;
    private MagicRedisProperties magicRedisProperties;

    MagicValueOperations(ValueOperations<String, String> valueOperations, MagicRedisProperties magicRedisProperties) {
        this.valueOperations = valueOperations;
        this.magicRedisProperties = magicRedisProperties;
    }

    @Override
    public void set(@NonNull String key, @NonNull String value) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        this.valueOperations.set(key, value, magicRedisProperties.getDefaultExpireTimeout());
    }

    @Override
    public void set(@NonNull String key, @NonNull String value, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        Assert.notNull(unit, "unit must not be null!");
        this.valueOperations.set(key, value, timeout, unit);
    }

    @Override
    public Boolean setIfAbsent(@NonNull String key, @NonNull String value) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        return this.valueOperations.setIfAbsent(key, value, magicRedisProperties.getDefaultExpireTimeout());
    }

    @Override
    public Boolean setIfAbsent(@NonNull String key, @NonNull String value, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        Assert.notNull(unit, "unit must not be null!");
        return this.valueOperations.setIfAbsent(key, value, timeout, unit);
    }

    @Override
    public Boolean setIfPresent(@NonNull String key, @NonNull String value) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        return this.valueOperations.setIfAbsent(key, value, magicRedisProperties.getDefaultExpireTimeout());
    }

    @Override
    public Boolean setIfPresent(@NonNull String key, @NonNull String value, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        Assert.notNull(unit, "unit must not be null!");
        return this.valueOperations.setIfPresent(key, value, timeout, unit);
    }

    @Override
    public void multiSet(@NonNull Map<? extends String, ? extends String> map) {
        multiSet(map, magicRedisProperties.getDefaultExpireTimeout());
    }

    public void multiSet(@NonNull Map<? extends String, ? extends String> map, @NonNull Duration timeout) {
        Assert.notNull(timeout, "Timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        multiSet(map, timeData.getTime(), timeData.getUnit());
    }

    public void multiSet(@NonNull Map<? extends String, ? extends String> map, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(map, "map must not be null!");
        this.valueOperations.multiSet(map);
        for (String key : map.keySet()) {
            getOperations().expire(key, timeout, unit);
        }
    }

    @Override
    public Boolean multiSetIfAbsent(@NonNull Map<? extends String, ? extends String> map) {
        return multiSetIfAbsent(map, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Boolean multiSetIfAbsent(@NonNull Map<? extends String, ? extends String> map, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return multiSetIfAbsent(map, timeData.getTime(), timeData.getUnit());
    }

    public Boolean multiSetIfAbsent(@NonNull Map<? extends String, ? extends String> map, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(map, "map must not be null!");
        Boolean result = this.valueOperations.multiSetIfAbsent(map);
        for (String key : map.keySet()) {
            getOperations().expire(key, timeout, unit);
        }
        return result;
    }

    @Override
    public String get(@NonNull Object key) {
        Assert.notNull(key, "key must not be null!");
        return this.valueOperations.get(key);
    }

    @Override
    public String getAndSet(@NonNull String key, @NonNull String value) {
        return getAndSet(key, value, magicRedisProperties.getDefaultExpireTimeout());
    }

    public String getAndSet(@NonNull String key, @NonNull String value, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return getAndSet(key, value, timeData.getTime(), timeData.getUnit());
    }

    public String getAndSet(@NonNull String key, @NonNull String value, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        String oldValue = this.valueOperations.getAndSet(key, value);
        getOperations().expire(key, timeout, unit);
        return oldValue;
    }

    @Override
    public List<String> multiGet(@NonNull Collection<String> keys) {
        Assert.notNull(keys, "keys must not be null!");
        return this.valueOperations.multiGet(keys);
    }

    @Override
    public Long increment(@NonNull String key) {
        return increment(key, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Long increment(@NonNull String key, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return increment(key, timeData.getTime(), timeData.getUnit());
    }

    public Long increment(@NonNull String key, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Long value = this.valueOperations.increment(key);
        getOperations().expire(key, timeout, unit);
        return value;
    }

    @Override
    public Long increment(@NonNull String key, long delta) {
        return increment(key, delta, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Long increment(@NonNull String key, long delta, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return increment(key, delta, timeData.getTime(), timeData.getUnit());
    }

    public Long increment(@NonNull String key, long delta, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Long value = this.valueOperations.increment(key, delta);
        getOperations().expire(key, timeout, unit);
        return value;
    }

    @Override
    public Double increment(@NonNull String key, double delta) {
        return increment(key, delta, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Double increment(@NonNull String key, double delta, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return increment(key, delta, timeData.getTime(), timeData.getUnit());
    }

    public Double increment(@NonNull String key, double delta, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Double value = this.valueOperations.increment(key, delta);
        getOperations().expire(key, timeout, unit);
        return value;
    }

    @Override
    public Long decrement(@NonNull String key) {
        return decrement(key, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Long decrement(@NonNull String key, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return decrement(key, timeData.getTime(), timeData.getUnit());
    }

    public Long decrement(@NonNull String key, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Long value = this.valueOperations.decrement(key);
        getOperations().expire(key, timeout, unit);
        return value;
    }

    @Override
    public Long decrement(@NonNull String key, long delta) {
        return decrement(key, delta, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Long decrement(@NonNull String key, long delta, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return decrement(key, delta, timeData.getTime(), timeData.getUnit());
    }

    public Long decrement(@NonNull String key, long delta, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Long value = this.valueOperations.decrement(key, delta);
        getOperations().expire(key, timeout, unit);
        return value;
    }

    @Override
    public Integer append(@NonNull String key, @NonNull String value) {
        return append(key, value, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Integer append(@NonNull String key, @NonNull String value, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return append(key, value, timeData.getTime(), timeData.getUnit());
    }

    public Integer append(@NonNull String key, @NonNull String value, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        Integer result = this.valueOperations.append(key, value);
        getOperations().expire(key, timeout, unit);
        return result;
    }

    @Override
    public String get(@NonNull String key, long start, long end) {
        Assert.notNull(key, "key must not be null!");
        return this.valueOperations.get(key, start, end);
    }

    @Override
    public void set(@NonNull String key, @NonNull String value, long offset) {
        set(key, value, offset, magicRedisProperties.getDefaultExpireTimeout());
    }

    public void set(@NonNull String key, @NonNull String value, long offset, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        set(key, value, offset, timeData.getTime(), timeData.getUnit());
    }

    public void set(@NonNull String key, @NonNull String value, long offset, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(value, "value must not be null!");
        this.valueOperations.set(key, value, offset);
        getOperations().expire(key, timeout, unit);
    }

    @Override
    public Long size(@NonNull String key) {
        Assert.notNull(key, "key must not be null!");
        return this.valueOperations.size(key);
    }

    @Override
    public Boolean setBit(@NonNull String key, long offset, boolean value) {
        return setBit(key, offset, value, magicRedisProperties.getDefaultExpireTimeout());
    }

    public Boolean setBit(@NonNull String key, long offset, boolean value, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return setBit(key, offset, value, timeData.getTime(), timeData.getUnit());
    }

    public Boolean setBit(@NonNull String key, long offset, boolean value, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Boolean result = this.valueOperations.setBit(key, offset, value);
        getOperations().expire(key, timeout, unit);
        return result;
    }

    @Override
    public Boolean getBit(@NonNull String key, long offset) {
        Assert.notNull(key, "key must not be null!");
        return this.valueOperations.getBit(key, offset);
    }

    @Override
    public List<Long> bitField(@NonNull String key, @NonNull BitFieldSubCommands subCommands) {
        return bitField(key, subCommands, magicRedisProperties.getDefaultExpireTimeout());
    }

    public List<Long> bitField(@NonNull String key, @NonNull BitFieldSubCommands subCommands, @NonNull Duration timeout) {
        Assert.notNull(timeout, "timeout must not be null!");
        DurationUtils.TimeData timeData = DurationUtils.toTimeData(timeout);
        return bitField(key, subCommands, timeData.getTime(), timeData.getUnit());
    }

    public List<Long> bitField(@NonNull String key, @NonNull BitFieldSubCommands subCommands, long timeout, @NonNull TimeUnit unit) {
        Assert.notNull(key, "key must not be null!");
        Assert.notNull(subCommands, "subCommands must not be null!");
        List<Long> result = this.valueOperations.bitField(key, subCommands);
        getOperations().expire(key, timeout, unit);
        return result;
    }

    @Override
    public @NonNull RedisOperations<String, String> getOperations() {
        return this.valueOperations.getOperations();
    }
}
